import socket
import struct
import time
import threading
import configparser
import os
from modules.camera import Camera
from modules.locker import Locker
from modules.logger_config import get_client_logger

# 配置日志
logger = get_client_logger()

# 异常代码常量
class ExceptionCodes:
    LOCKER_CONNECTION_FAILED = 401  # 电磁锁连接失败
    CAMERA_INIT_FAILED = 402        # 摄像头初始化失败
    SERIAL_COMM_ERROR = 403         # 串口通信异常
    DOOR_STATUS_ERROR = 404         # 门状态检测异常
    VIDEO_RECORD_FAILED = 405       # 视频录制失败
    NETWORK_ERROR = 406             # 网络连接异常
    HARDWARE_FAULT = 407            # 硬件设备故障
    RESOURCE_INSUFFICIENT = 408     # 系统资源不足
    CONFIG_ERROR = 409              # 配置文件错误
    OTHER_SYSTEM_ERROR = 410        # 其他系统异常
    VIDEO_UPLOAD_FAILED = 411       # 录像上传失败
    VIDEO_UPLOAD_ERROR = 412        # 上传录像出错


class Client:
    def __init__(self, config_file="config.ini"):
        # 加载配置文件
        self.config = configparser.ConfigParser()
        self.config.read(config_file, encoding='utf-8')
        
        # 从配置文件读取参数
        self.host = self.config.get('server', 'host')
        self.port = self.config.getint('server', 'port')
        self.terminal_id = self.config.get('terminal', 'terminal_id')
        self.heartbeat_interval = self.config.getint('terminal', 'heartbeat_interval')
        self.login_retry_interval = self.config.getint('terminal', 'login_retry_interval')
        self.debug_mode = self.config.getboolean('terminal', 'debug_mode')
        
        # 从配置文件读取门状态超时参数
        self.door_open_timeout = self.config.getint('locker', 'door_open_timeout')
        self.door_close_timeout = self.config.getint('locker', 'door_close_timeout')
        
        self.socket = None
        self.connected = False
        self.logged_in = False  # 登录状态
        self.heartbeat_thread = None
        self.login_retry_thread = None
        
        # 初始化硬件组件
        self.locker = Locker(config_file)
        self.camera = Camera(config_file)
        
        # 业务状态管理
        self.current_command_id = None

        self.recording_active = False
        self.video_filename = None
        self.server_filename = None
        
        # 开门业务流程线程控制
        self.door_process_thread = None
        self.door_process_running = False
        
        # 异常处理
        self.exception_callbacks = {}
        
        # 登录重试控制
        self.login_retry_active = False
        
    def connect(self):
        """连接到服务器"""
        try:
            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.socket.connect((self.host, self.port))
            self.connected = True
            self.logged_in = False  # 重置登录状态
            logger.info(f"已连接到服务器 {self.host}:{self.port}")
            if self.debug_mode:
                logger.info("🔧 DEBUG模式已启用 - 将跳过硬件初始化")
            
            # 启动接收消息线程（必须在发送登录请求之前启动）
            receive_thread = threading.Thread(target=self._receive_messages, daemon=True)
            receive_thread.start()
            
            # 发送登录请求
            self.send_login()
            
            # 启动登录重试线程
            self.login_retry_active = True
            self.login_retry_thread = threading.Thread(target=self._login_retry_loop, daemon=True)
            self.login_retry_thread.start()
            
            return True
        except Exception as e:
            logger.error(f"连接服务器失败: {e}")
            return False
    
    def disconnect(self):
        """断开连接"""
        self.connected = False
        self.logged_in = False
        self.login_retry_active = False
        
        # 停止录像（如果正在录像）
        if self.recording_active:
            self._stop_recording()
        
        # 等待开门业务流程线程结束
        if self.door_process_thread and self.door_process_thread.is_alive():
            logger.info("等待开门业务流程线程结束...")
            self.door_process_thread.join(timeout=5)  # 最多等待5秒
            if self.door_process_thread.is_alive():
                logger.warning("开门业务流程线程未能在5秒内结束")
        
        # 断开硬件连接（只有在登录成功后才需要断开）
        if not self.debug_mode:
            try:
                self.locker.disconnect()
                logger.info("电磁锁连接已断开")
            except Exception as e:
                logger.warning(f"断开电磁锁连接时出错: {e}")
            
            try:
                self.camera.release_camera()
                logger.info("摄像头已释放")
            except Exception as e:
                logger.warning(f"释放摄像头时出错: {e}")
        else:
            logger.info("🔧 DEBUG模式：跳过硬件释放")
        
        if self.socket:
            self.socket.close()
            self.socket = None
        logger.info("已断开与服务器的连接")
    
    def build_frame(self, control_code, data_str=""):
        """构建协议帧"""
        # 帧起始符
        frame = bytearray([0xFA])
        
        # 控制码
        frame.append(control_code)
        
        # 数据域长度 (2字节，大端序)
        data_bytes = data_str.encode('utf-8') if data_str else b''
        data_length = len(data_bytes)
        frame.extend(struct.pack('>H', data_length))
        
        # 数据域
        frame.extend(data_bytes)
        
        # 帧结束符
        frame.append(0xFF)
        
        return frame
    
    def parse_frame(self, frame):
        """解析协议帧"""
        if len(frame) < 5:
            return None
        
        # 检查帧起始符和结束符
        if frame[0] != 0xFA or frame[-1] != 0xFF:
            return None
        
        control_code = frame[1]
        data_length = struct.unpack('>H', frame[2:4])[0]
        
        # 检查数据长度是否匹配
        if len(frame) != 5 + data_length:
            return None
        
        data = frame[4:4+data_length].decode('utf-8') if data_length > 0 else ""
        
        return {
            'control_code': control_code,
            'data_length': data_length,
            'data': data
        }
    
    def send_frame(self, frame):
        """发送帧数据"""
        try:
            self.socket.sendall(frame)
            return True
        except Exception as e:
            logger.error(f"发送数据失败: {e}")
            self.connected = False
            return False
    
    def send_login(self):
        """发送登录请求"""
        frame = self.build_frame(0x01, self.terminal_id)
        return self.send_frame(frame)
    
    def send_heartbeat(self):
        """发送心跳"""
        frame = self.build_frame(0x03)
        return self.send_frame(frame)
    
    def send_open_door_response(self, command_id, status):
        """发送开门响应
        status: 1-开门成功, 2-开门失败
        """
        data = f"{command_id}:{status}"
        frame = self.build_frame(0x06, data)
        return self.send_frame(frame)
    
    def send_close_door_info(self, status):
        """发送关门信息
        status: 1-关门成功, 2-关门失败
        """
        # 使用服务器返回的文件名（如果上传成功）或原始文件名
        filename = self.server_filename if self.server_filename else "unknown"
        data = f"{self.current_command_id}:{self.terminal_id}:{status}:{filename}"
        frame = self.build_frame(0x07, data)
        return self.send_frame(frame)
    
    def send_exception_info(self, error_code):
        """发送异常信息
        error_code: 异常代码
        """
        data = str(error_code)
        frame = self.build_frame(0x0A, data)
        logger.warning(f"发送异常信息: 代码={error_code}")
        return self.send_frame(frame)
    
    def report_exception(self, error_code, error_desc=None):
        """报告异常
        error_code: 异常代码
        error_desc: 异常描述，如果为None则使用默认描述（仅用于日志记录）
        """
        if error_desc is None:
            error_descriptions = {
                ExceptionCodes.LOCKER_CONNECTION_FAILED: "电磁锁连接失败",
                ExceptionCodes.CAMERA_INIT_FAILED: "摄像头初始化失败",
                ExceptionCodes.SERIAL_COMM_ERROR: "串口通信异常",
                ExceptionCodes.DOOR_STATUS_ERROR: "门状态检测异常",
                ExceptionCodes.VIDEO_RECORD_FAILED: "视频录制失败",
                ExceptionCodes.NETWORK_ERROR: "网络连接异常",
                ExceptionCodes.HARDWARE_FAULT: "硬件设备故障",
                ExceptionCodes.RESOURCE_INSUFFICIENT: "系统资源不足",
                ExceptionCodes.CONFIG_ERROR: "配置文件错误",
                ExceptionCodes.OTHER_SYSTEM_ERROR: "其他系统异常",
                ExceptionCodes.VIDEO_UPLOAD_FAILED: "录像上传失败",
                ExceptionCodes.VIDEO_UPLOAD_ERROR: "上传录像出错"
            }
            error_desc = error_descriptions.get(error_code, "未知异常")
        
        # 调用异常回调
        if error_code in self.exception_callbacks:
            self.exception_callbacks[error_code](error_code, error_desc)
        
        # 发送异常信息到服务器（只发送异常代码）
        return self.send_exception_info(error_code)
    
    def set_exception_callback(self, error_code, callback):
        """设置异常回调函数
        error_code: 异常代码
        callback: 回调函数，参数为(error_code, error_desc)
        """
        self.exception_callbacks[error_code] = callback
    
    def _login_retry_loop(self):
        """登录重试循环"""
        while self.connected and not self.logged_in and self.login_retry_active:
            logger.info(f"等待 {self.login_retry_interval} 秒后重试登录...")
            time.sleep(self.login_retry_interval)
            
            if self.connected and not self.logged_in and self.login_retry_active:
                logger.info("重试登录...")
                self.send_login()
    
    def _start_post_login_services(self):
        """启动登录成功后的服务"""
        if not self.logged_in:
            return
            
        # 停止登录重试线程
        self.login_retry_active = False
        
        if self.debug_mode:
            logger.info("🔧 DEBUG模式：跳过硬件初始化，仅启动心跳线程")
        else:
            # 连接硬件组件
            logger.info("开始初始化硬件组件...")
            
            # 连接电磁锁
            if not self.locker.connect():
                logger.warning("电磁锁连接失败，但继续运行")
                self.report_exception(ExceptionCodes.LOCKER_CONNECTION_FAILED)
            else:
                logger.info("电磁锁连接成功")
                # 设置电磁锁状态回调
                self.locker.set_status_callback(self._on_door_status_change)
            
            # 初始化摄像头
            if not self.camera.initialize_camera():
                logger.warning("摄像头初始化失败，但继续运行")
                self.report_exception(ExceptionCodes.CAMERA_INIT_FAILED)
            else:
                logger.info("摄像头初始化成功")
        
        # 启动心跳线程（无论是否debug模式都需要）
        if not self.heartbeat_thread or not self.heartbeat_thread.is_alive():
            self.heartbeat_thread = threading.Thread(target=self._heartbeat_loop, daemon=True)
            self.heartbeat_thread.start()
            logger.info("心跳线程已启动")
        
        if self.debug_mode:
            logger.info("🔧 DEBUG模式：登录成功后的服务已启动（仅通信功能）")
        else:
            logger.info("登录成功后的所有服务已启动")

    def _heartbeat_loop(self):
        """心跳循环"""
        while self.connected and self.logged_in:
            time.sleep(self.heartbeat_interval)
            if self.connected and self.logged_in:
                self.send_heartbeat()
    
    def _receive_messages(self):
        """接收服务器消息"""
        buffer = bytearray()
        while self.connected:
            try:
                data = self.socket.recv(1024)
                if not data:
                    logger.warning("连接已断开")
                    self.connected = False
                    break
                
                buffer.extend(data)
                
                # 处理缓冲区中的所有完整帧
                while buffer:
                    # 查找帧起始符
                    start_idx = buffer.find(0xFA)
                    if start_idx == -1:
                        buffer.clear()
                        break
                    
                    # 移除起始符之前的所有数据
                    if start_idx > 0:
                        buffer = buffer[start_idx:]
                    
                    # 检查是否有足够的数据获取数据长度
                    if len(buffer) < 5:  # 至少需要5字节(FA+控制码+2字节长度+FF)
                        break
                    
                    # 获取数据长度
                    data_length = struct.unpack('>H', buffer[2:4])[0]
                    frame_length = 5 + data_length  # 帧总长度
                    
                    # 检查是否收到了完整的一帧
                    if len(buffer) < frame_length:
                        break
                    
                    # 提取完整帧
                    frame_data = buffer[:frame_length]
                    buffer = buffer[frame_length:]
                    
                    # 解析帧
                    parsed = self.parse_frame(frame_data)
                    if parsed:
                        self._handle_frame(parsed)
                    else:
                        logger.warning("解析帧失败")
                        
            except Exception as e:
                logger.error(f"接收数据错误: {e}")
                self.connected = False
                break
    
    def _handle_frame(self, frame):
        """处理接收到的帧"""
        control_code = frame['control_code']
        data = frame['data']

        if not control_code == 0x04:
            logger.info(f"收到控制码: 0x{control_code:02X}, 数据: {data}")
        
        if control_code == 0x02:  # 登录响应
            if data == "SUCCESS":
                logger.info("登录成功")
                self.logged_in = True
                # 启动登录成功后的服务
                self._start_post_login_services()
            else:
                logger.error(f"登录失败: {data}")
                self.logged_in = False
                # 登录失败时不立即断开连接，让重试线程处理
                
        elif control_code == 0x04:  # 心跳响应
            logger.debug("收到心跳响应")
            
        elif control_code == 0x05:  # 开门指令
            if not self.logged_in:
                logger.warning("未登录，忽略开门指令")
                return
            parts = data.split(':', 1)
            if len(parts) == 2:
                command_id, door_id = parts
                logger.info(f"收到开门指令: 指令ID={command_id}, 柜门编号={door_id}")
                if self.debug_mode:
                    logger.info("🔧 DEBUG模式：模拟开门业务流程")
                    # 在debug模式下模拟开门响应
                    self.send_open_door_response(command_id, 1)  # 模拟开门成功
                else:
                    # 启动开门业务流程
                    self._start_door_open_process(command_id, door_id)
            else:
                logger.warning("开门指令格式错误")
        elif control_code == 0x08:  # 关门信息响应
            logger.info(f"收到关门信息响应: {data}")
        elif control_code == 0x0A:  # 异常信息（来自服务器）
            self._handle_exception_info(data)
        elif control_code == 0x0B:  # 异常信息响应
            self._handle_exception_response(data)
        else:
            logger.warning(f"未知的控制码: 0x{control_code:02X}")
    
    def _start_door_open_process(self, command_id, door_id):
        """启动开门业务流程"""
        self.current_command_id = command_id
        if door_id != self.terminal_id:
            return
            
        # 检查是否已有开门业务流程在运行
        if self.door_process_running:
            logger.warning("开门业务流程已在运行中，忽略新的开门指令")
            return
            
        # 在独立线程中执行业务流程，避免阻塞主线程
        self.door_process_thread = threading.Thread(
            target=self._door_open_business_process,
            args=(command_id, door_id),
            daemon=True
        )
        self.door_process_running = True
        self.door_process_thread.start()
    
    def _door_open_business_process(self, command_id, door_id):
        """开门业务流程"""
        try:
            logger.info(f"开始开门业务流程: 指令ID={command_id}, 柜门={door_id}")
            
            # 1. 控制电磁锁开锁
            logger.info("步骤1: 控制电磁锁开锁")
            unlock_success = self.locker.unlock_door()
            
            # 2. 向服务器发送开门响应
            logger.info("步骤2: 向服务器发送开门响应")
            status = 1 if unlock_success else 2
            self.send_open_door_response(command_id, status)
            
            if not unlock_success:
                logger.error("开锁失败，业务流程终止")
                return
            
            # 3. 等待柜门打开状态
            logger.info("步骤3: 等待柜门打开状态")
            if self._wait_for_door_state("OPEN", timeout=self.door_open_timeout):
                logger.info("柜门已打开")
                
                # 4. 摄像头开始录像
                logger.info("步骤4: 摄像头开始录像")
                self._start_recording()
                
                # 5. 等待柜门关闭状态
                logger.info("步骤5: 等待柜门关闭状态")
                if self._wait_for_door_state("CLOSED", timeout=self.door_close_timeout):
                    logger.info("柜门已关闭")
                    
                    # 6. 停止录像
                    logger.info("步骤6: 停止录像")
                    self._stop_recording()
                    
                    # 7. 控制电磁锁关锁
                    logger.info("步骤7: 控制电磁锁关锁")
                    lock_success = self.locker.lock_door()
                    
                    # 8. 上传录像文件
                    logger.info("步骤8: 上传录像文件")
                    upload_success = self._upload_recording()
                    
                    # 9. 向服务器发送关门信息
                    logger.info("步骤9: 向服务器发送关门信息")
                    close_status = 1 if lock_success else 2
                    self.send_close_door_info(close_status)
                    
                    logger.info("开门业务流程完成")
                else:
                    logger.warning("等待柜门关闭超时")
                    self._stop_recording()
            else:
                logger.warning("等待柜门打开超时")
                
        except Exception as e:
            logger.error(f"开门业务流程出错: {e}")
            # 确保停止录像
            if self.recording_active:
                self._stop_recording()
        finally:
            # 清理线程状态
            self.door_process_running = False
            self.door_process_thread = None
            logger.info("开门业务流程线程已结束")
    
    def _wait_for_door_state(self, target_state, timeout=10):
        """等待柜门状态变化"""
        start_time = time.time()
        while time.time() - start_time < timeout:
            if target_state == "OPEN" and self.locker.opened:
                return True
            elif target_state == "CLOSED" and not self.locker.opened:
                return True
            time.sleep(0.5)
        return False
    
    def _start_recording(self):
        """开始录像"""
        try:
            timestamp = time.strftime("%Y%m%d%H%M%S")
            self.video_filename = f"{self.terminal_id}_{timestamp}"
            success, message, video_path = self.camera.start_recording(self.video_filename)
            
            if success:
                self.recording_active = True
                logger.info(f"开始录像: {video_path}")
            else:
                logger.error(f"开始录像失败: {message}")
                self.report_exception(ExceptionCodes.VIDEO_RECORD_FAILED, f"开始录像失败: {message}")
                
        except Exception as e:
            logger.error(f"开始录像出错: {e}")
    
    def _stop_recording(self):
        """停止录像"""
        try:
            if self.recording_active:
                success, message, video_path = self.camera.stop_recording()
                self.recording_active = False
                
                if success:
                    logger.info(f"停止录像: {video_path}")
                else:
                    logger.error(f"停止录像失败: {message}")
                    
        except Exception as e:
            logger.error(f"停止录像出错: {e}")
    
    
    def _upload_recording(self):
        """上传录像文件
        将录制的视频文件上传到服务器，并更新server_filename为服务器返回的文件名
        返回: bool - 上传是否成功
        """
        try:
            if not self.video_filename:
                logger.warning("没有录像文件需要上传")
                return False
            
            # 使用camera的upload_file方法上传录像
            success, message, response_data, server_filename = self.camera.upload_file(
                f"records/{self.video_filename}.mp4",
                f"{self.video_filename}.mp4"
            )
            
            if success:
                logger.info(f"录像上传成功: {message}")
                if server_filename:
                    logger.info(f"服务器文件名: {server_filename}")
                    # 更新server_filename为服务器返回的文件名
                    self.server_filename = server_filename
                return True
            else:
                logger.error(f"录像上传失败: {message}")
                # 上传失败时报告录像上传失败异常
                self.report_exception(ExceptionCodes.VIDEO_UPLOAD_FAILED, f"录像上传失败: {message}")
                return False
                
        except Exception as e:
            logger.error(f"上传录像出错: {e}")
            self.report_exception(ExceptionCodes.VIDEO_UPLOAD_ERROR, f"上传录像出错: {e}")
            return False
    
    def _handle_exception_info(self, data):
        """处理来自服务器的异常信息"""
        parts = data.split(':', 1)
        if len(parts) == 2:
            error_code, error_desc = parts
            logger.warning(f"收到服务器异常信息: 代码={error_code}, 描述={error_desc}")
            print(f"🚨 服务器异常信息: 代码={error_code}, 描述={error_desc}")
        else:
            logger.warning(f"异常信息格式错误: {data}")
    
    def _handle_exception_response(self, data):
        """处理异常信息响应"""
        parts = data.split(':', 1)
        if len(parts) == 2:
            error_code, response = parts
            logger.info(f"收到异常信息响应: 代码={error_code}, 处理结果={response}")
            print(f"📋 异常信息响应: 代码={error_code}, 处理结果={response}")
            
            # 根据处理结果执行相应操作
            if response == "RESTART":
                logger.warning("服务器建议重启终端")
                print("⚠️  服务器建议重启终端")
                # 重启终端
                self.disconnect()
                logger.info(f"等待 {self.login_retry_interval} 秒后重启终端...")
                time.sleep(self.login_retry_interval)
                self.connect()
            elif response == "MAINTENANCE":
                logger.warning("服务器建议进行维护")
                print("⚠️  服务器建议进行维护")
            elif response == "IGNORE":
                logger.info("服务器建议忽略该异常")
                print("ℹ️  服务器建议忽略该异常")
            elif response == "ACK":
                logger.info("服务器已确认接收异常信息")
                print("✅️  服务器已确认接收异常信息")
        else:
            logger.warning(f"异常信息响应格式错误: {data}")

    def _on_door_status_change(self, status):
        """电磁锁状态变化回调"""
        logger.info(f"柜门 {self.terminal_id} 状态变化: {status}")


# 使用示例
if __name__ == "__main__":
    # 创建客户端实例（从配置文件读取参数）
    client = Client()
    
    # 连接服务器
    if client.connect():
        try:
            print("\n=== 自动售货机客户端测试程序 ===")
            print("命令说明:")
            print("  t - 测试开门业务流程")
            print("  s - 查看当前状态")
            print("  e <code> - 测试异常报告 (401-412)")
            print("  q - 退出程序")
            print("  h - 显示帮助")
            print("===============================\n")
            
            # 主循环
            while client.connected:
                try:
                    user_input = input("请输入命令 (h查看帮助): ").strip().lower()
                    
                    if user_input == 'q':
                        print("正在退出...")
                        break
                    elif user_input == 'h':
                        print("\n命令说明:")
                        print("  t - 测试开门业务流程")
                        print("  s - 查看当前状态")
                        print("  e <code> - 测试异常报告 (401-412)")
                        print("  q - 退出程序")
                        print("  h - 显示帮助")
                        print()
                    elif user_input == 's':
                        print(f"\n当前状态:")
                        print(f"  连接状态: {'已连接' if client.connected else '未连接'}")
                        print(f"  登录状态: {'已登录' if client.logged_in else '未登录'}")
                        print(f"  运行模式: {'🔧 DEBUG模式' if client.debug_mode else '正常模式'}")
                        print(f"  终端ID: {client.terminal_id}")
                        print(f"  当前指令ID: {client.current_command_id}")
                        print(f"  开门业务流程运行中: {'是' if client.door_process_running else '否'}")
                        if not client.debug_mode:
                            print(f"  录像状态: {'录像中' if client.recording_active else '未录像'}")
                            print(f"  锁状态: {'关闭' if client.locker.locked else '打开'}")
                            print(f"  门状态: {'打开' if client.locker.opened else '关闭'}")
                        else:
                            print(f"  录像状态: DEBUG模式 - 跳过")
                            print(f"  锁状态: DEBUG模式 - 跳过")
                            print(f"  门状态: DEBUG模式 - 跳过")
                        print()
                    elif user_input == 't':
                        if client.door_process_running:
                            print("开门业务流程正在运行中，请等待完成后再试")
                        else:
                            if client.debug_mode:
                                print("🔧 DEBUG模式：模拟开门业务流程（跳过硬件操作）")
                            else:
                                print("开始测试开门业务流程...")
                            # 模拟收到开门指令
                            test_command_id = f"T{int(time.time())}"
                            test_door_id = client.terminal_id
                            print(f"模拟开门指令: 指令ID={test_command_id}, 柜门={test_door_id}")
                            if client.debug_mode:
                                # 在debug模式下直接发送开门响应
                                client.send_open_door_response(test_command_id, 1)
                                print("🔧 DEBUG模式：已发送模拟开门成功响应")
                            else:
                                client._start_door_open_process(test_command_id, test_door_id)
                    elif user_input.startswith('e '):
                        # 测试异常报告
                        parts = user_input.split()
                        if len(parts) >= 2:
                            try:
                                error_code = int(parts[1])
                                if 401 <= error_code <= 412:
                                    print(f"测试异常报告: 代码={error_code}")
                                    client.report_exception(error_code)
                                else:
                                    print("异常代码必须在401-412范围内")
                            except ValueError:
                                print("异常代码必须是数字")
                        else:
                            print("用法: e <code> (例如: e 401)")
                    else:
                        print("未知命令，输入 'h' 查看帮助")
                        
                except EOFError:
                    # 处理 Ctrl+D 输入
                    print("\n正在退出...")
                    break
                except KeyboardInterrupt:
                    print("\n用户中断，正在退出...")
                    break
                    
        except KeyboardInterrupt:
            logger.info("用户中断连接")
        finally:
            client.disconnect()
    else:
        logger.error("无法连接到服务器，程序退出")